package vivace.model;

import java.util.*;
import java.io.*;
import javax.sound.midi.*;

import vivace.model.Project.Action;

public class App extends Observable {

	/**
	 * Enumerator that explains the supported operations for this class.	
	 */
	public enum Action{
		ALL_CLOSED, ALL_SAVED, PROJECT_ADDED, PROJECT_CLOSED, 
		PROJECT_ACTIVATED, PROJECT_SAVED, PROJECT_PLAYED, 
		PROJECT_STOPPED, PROJECT_PERSPECTIVE_CHANGED;
	}
	
	private HashMap<Integer,Project> projects;
	private Set<Integer> projectHistory;
	private Integer activeProjectKey;
	private Action action;
	
	/**
	 * @return The last performed operation for the current instance. This value can be used
	 * in the observer's update-method to see what kind of operation that was performed.
	 */
	public Action getAction() {
		Action current = this.action;
		return current;
	}

	/**
	 * @return The key of the active project
	 */
	public Integer getActiveProjectKey(){
		return activeProjectKey;
	}
	
	/**
	 * @return The active project
	 */
	public Project getActiveProject(){
		return projects.get(activeProjectKey);
	}
	
	/**
	 * Sets the active project
	 * @param key The key of the project that should be set to active
	 */
	public void setActiveProject( Integer key ) {
		if (!key.equals(this.activeProjectKey))
			this.setChanged(); // Only update if necessary
		this.activeProjectKey = key;
		if( key > 0 )
			this.action = Action.PROJECT_ACTIVATED;
		else
			this.action = Action.ALL_CLOSED;
		this.notifyObservers( key ); 
	}

	/**
	 * @param key The key of the project
	 * @return The project with the corresponding key
	 */
	public Project getProject( Integer key ){
		return this.projects.get(key);
	}
	
	/**
	 * @return An iterator of all opened projects
	 */
	public Iterator<Map.Entry<Integer, Project>> getProjectsIterator(){
		return this.projects.entrySet().iterator();
	}
	
	public void setPerspective(String perspective) {
		Project activeProject = getActiveProject();
		activeProject.setPerspective(perspective);
		this.action = Action.PROJECT_PERSPECTIVE_CHANGED;
		this.setChanged();
		this.notifyObservers();
	}
	
	/**
	 * Constructor
	 */
	public App(){
		this.projects = new HashMap<Integer,Project>();
		this.activeProjectKey = 0;
	}

	/**
	 * Adds an empty project to the list of projects and assigns a key to it
	 */
	public void addProject(){
		Project p = new Project();
		Integer newKey = new Integer(this.projects.size()+1);
		this.projects.put(newKey,p);
		p.setName("Untitled " + newKey.toString());
		this.setChanged();
		this.action = Action.PROJECT_ADDED;
		this.notifyObservers( newKey );
		setActiveProject(newKey);
	}
	
	/**
	 * Reads the file specified by the file path and creates a new project from the file.
	 * The project is assigned a key and added to the list of projects.
	 * @param filePath The path to the file that should be read
	 */
	public void addProject(File file){
		
		Project p = new Project(file);
		Integer newKey = new Integer(this.projects.size()+1);
		this.projects.put(newKey,p);
		p.setName(file.getName());
		this.setChanged();
		this.action = Action.PROJECT_ADDED;
		this.notifyObservers( newKey );
		setActiveProject(newKey);
	}
	
	/**
	 * Closes all open projects
	 */
	public void closeAll(){
		// TODO: Kolla om osparat osv...
		
		// fetch active project
		Project p = this.getActiveProject();
		
		// stop project, if playing, before removing
		if (p.isPlaying()) {
			p.stop();
		}
		
		this.projects.clear();
		setActiveProject(0);
		
		this.action = Action.PROJECT_CLOSED;
		this.setChanged();
		this.notifyObservers();
	}
	
	/**
	 * Closes the active project
	 */
	public void closeActive(){
		// TODO: Kolla om osparat osv...
		// fetch active project
		Project p = this.getActiveProject();
		
		// stop project, if playing, before removing
		if (p.isPlaying()) {
			p.stop();
		}
		
		this.projects.remove(this.activeProjectKey);
		this.setChanged();
		this.action = Action.PROJECT_CLOSED;
		this.notifyObservers(this.activeProjectKey);
		setActiveProject(getNewActive());
	}
	
	/**
	 * Saves all opened projects
	 */
	public void saveAll() throws IOException{
		Iterator<Project> ps = this.projects.values().iterator();
		Project p;
		while(ps.hasNext()){
			p = ps.next();
			p.save();
		}
		this.setChanged();
		this.action = Action.ALL_SAVED;
		this.notifyObservers();
	}
	
	/***
	 * Saves the active project
	 */
	public void saveActive() throws IOException{
		Project p = this.projects.get(this.activeProjectKey);
		p.save();
		this.setChanged();
		this.action = Action.PROJECT_SAVED;
		this.notifyObservers(this.activeProjectKey);
	}
	
	/***
	 * Saves the active project into a new file
	 * @param file
	 */
	public void saveActive( File file ) throws IOException{
		Project p = this.projects.get(this.activeProjectKey);
		p.save(file);
		this.setChanged();
		this.action = Action.PROJECT_SAVED;
		this.notifyObservers(this.activeProjectKey);
	}
	
	/***
	 * Starts playback of the current project
	 * @param file
	 */
	public void playActive(){
		Project p = this.projects.get(this.activeProjectKey);
		p.play();
		this.setChanged();
		this.action = Action.PROJECT_PLAYED;
		this.notifyObservers(this.activeProjectKey);
	}
	
	/***
	 * Pauses playback of the current project
	 * @param file
	 */
	public void pauseActive(){
		Project p = this.projects.get(this.activeProjectKey);
		p.pause();
		
		this.action = Action.PROJECT_STOPPED;
		
		this.setChanged();
		this.notifyObservers(this.activeProjectKey);
	}

	/***
	 * Stops playback of the current project
	 * @param file
	 */
	public void stopActive(){
		Project p = this.projects.get(this.activeProjectKey);
		p.stop();
		
		this.action = Action.PROJECT_STOPPED;
		this.setChanged();
		this.notifyObservers(this.activeProjectKey);
	}
	
	public void setZoomLevel(int zoomLevel) {
		
		Project activeProject = getActiveProject();
		
		activeProject.setZoomLevel(zoomLevel);

		this.setChanged();
		this.notifyObservers();
	}
	
	/**
	 * Used to turn off all sounding notes.
	 * Useful when there are stuck notes playing on some synthesizers
	 */
	public void panic() {
		Project p = getActiveProject();
		
		if (p.isPlaying()) 
			p.stop();
		
		MidiChannel[] channels = getSynthesizer().getChannels();
	
		for(MidiChannel ch : channels) {
			ch.allSoundOff();
		}
		
		this.action = Action.PROJECT_STOPPED;
		this.setChanged();
		this.notifyObservers();
	}
	
	// TODO: synthesizer should be in the App model instead
	public Synthesizer getSynthesizer() {
		return getActiveProject().getSynthesizer();
	}

	public String[] getMidiDeviceList() {
		MidiDevice.Info[] inf = MidiSystem.getMidiDeviceInfo();
		String[] otherStrings, infoStrings = new String[inf.length];
		int offset = 0, index = 0;
		if (inf.length > 0) {
			for (; index < inf.length; index++) {
			    try {
				if (MidiSystem.getMidiDevice(inf[index]) instanceof Synthesizer) {
					infoStrings[index - offset] = inf[index].toString();
				}
				else {
					offset++;
				}
			    } catch (MidiUnavailableException e) { 
				offset++; 
			    }
			}
			if (offset > 0) {
				otherStrings = new String[index - offset];
				for (index = 0; index < otherStrings.length; index++) {
					otherStrings[index] = infoStrings[index];
				}
				return otherStrings;
			}
			return infoStrings;
		}
		return null;
	}	
	
	// Returns the key of the first project in the list
	private Integer getNewActive(){
		Integer i = 0;
		Iterator<Integer> it = this.projects.keySet().iterator();
		if( it.hasNext() )
			i = it.next();
		return i;
	}

}
